package com.googlecode.sparda.commons.io.stream;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Abstract class representing a managed output stream.
 * A managed output stream is a stream that can be closed and opened more times,
 * typically needed when opening a large number of file.
 * A {@link ManagedOutputStream} is managed by an {@link OutputStreamManager}.
 * @author Fabio Grucci (<i>alias</i> <b>Phoenix</b>)
 * @version 0.0.1
 * @since 0.0.1
 * @see OutputStreamManager
 */
public abstract class ManagedOutputStream<T extends OutputStream> extends OutputStream {
	/**
	 * The manager of this OutputStream {@link OutputStreamManager}.
	 */
	private final OutputStreamManager manager;
	/**
	 * Delegate Stream used.
	 */
	private T delegate;
	/**
	 * Build a {@link ManagedOutputStream}.
	 * @param delegate The delegate stream used in writing
	 * @throws IOException If raised a {@link IOException}
	 */
	public ManagedOutputStream(OutputStreamManager manager, T delegate) throws IOException {
		this.manager = manager;
		this.delegate = delegate;
		this.manager.notifyNewManagedOutputStream(this);
	}
	/*
	 * (non-Javadoc)
	 * @see java.io.OutputStream#write(int)
	 */
	@Override
	public void write(int b) throws IOException {
		checkThatDelegateIsOpened();
		this.delegate.write(b);
	}
	/*
	 * (non-Javadoc)
	 * @see java.io.OutputStream#write(byte[])
	 */
	@Override
	public void write(byte[] b) throws IOException {
		checkThatDelegateIsOpened();
		this.delegate.write(b);
	}
	/*
	 * (non-Javadoc)
	 * @see java.io.OutputStream#write(byte[], int, int)
	 */
	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		checkThatDelegateIsOpened();
		this.delegate.write(b,off,len);
		this.delegate.flush();
	}
	/*
	 * (non-Javadoc)
	 * @see java.io.OutputStream#flush()
	 */
	@Override
	public void flush() throws IOException {
		checkThatDelegateIsOpened();
		this.delegate.flush();
	}
	/*
	 * (non-Javadoc)
	 * @see java.io.OutputStream#close()
	 */
	@Override
	public void close() throws IOException {
		checkThatDelegateIsOpened();
		this.delegate.flush();
		this.delegate.close();
//		this.delegate = null; // distinct from temporary closure
		this.manager.notifyDefinitevelyClosure(this);
	}
	/**
	 * Method that returns an {@link OutputStream} to reopen the {@link OutputStream}.
	 * @return The {@link OutputStream} reopened
	 * @throws IOException If raise an {@link IOException}
	 */
	protected abstract T reopenDelegate() throws IOException;
	
	/**
	 * Method used by {@link OutputStreamManager} to temporary close this stream.
	 * @throws IOException If raised an {@link IOException}
	 */
	void temporaryClosure() throws IOException {
		if (isTemporaryClosed())
			throw new IllegalStateException("Stream " + this.getClass().getName() + " shouldn't be already closed!");
		this.delegate.flush();
		this.delegate.close();
		this.delegate = null;
		this.manager.notifyTemporaryClosure(this);
	}
	/**
	 * Method that checks if the current stream is temporary closed, if it is so 
	 * then is reopened using the {@link #reopenDelegate()} method.
	 * @throws IOException If raised an {@link IOException}
	 */
	private void checkThatDelegateIsOpened() throws IOException {
		if (this.isTemporaryClosed()) {
			this.manager.notifyReOpened(this);
			this.delegate = this.reopenDelegate();
		}
	}
	/**
	 * Method that returns <code>true</code> if the stream is temporary closed, <code>false</code> otherwise.
	 * The method returns <code>false</code> if the stream is permanently closed.
	 * @return <code>true</code> if the stream is temporary closed <code>false</code> otherwise
	 */
	public final boolean isTemporaryClosed() {
		return this.delegate==null;
	}
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getClass().getName()+" "+(this.delegate!=null?"Open":"Temporary closed");
	}	
}
